#张量是一种特殊的数据结构，与数组和矩阵非常相似。在 PyTorch 中，我们使用张量来编码模型的输入和输出，以及模型的参数。
#张量类似于 NumPy 的 ndarray，但张量可以在 GPU 或其他硬件加速器上运行。事实上，张量和 NumPy 数组通常可以共享相同的底层内存，从而无需复制数据

#基本模块导入

import torch
import numpy as np

#检测设备可用性
device = (
    "cuda"
    if torch.cuda.is_available()
    else "mps"
    if torch.backends.mps.is_available()
    else "cpu"
)
print(f"Using {device} devices")

# #张量使用的例程

# # 1. 创建张量

# #方法一：从数据直接创建  torch.tensor(data, dtype=None, device=None, requires_grad=False)
# data = [[1,2,3],[4,5,6],[7,8,9]]
# x_data = torch.tensor(data,device=device) #这个张量是直接从数据中创建的,并且默认的 requires_grad 参数为 False，这意味着 PyTorch 不会跟踪这个张量上的操作，因此不会为这个张量计算梯度。

# #从Numpy数组创建张量
# np_array = np.array(data)
# x_data = torch.from_numpy(np_array) #这个张量是从 NumPy 数组中创建的，因此 requires_grad 参数默认为 False。

# #创建一个未初始化的 3x3 张量
# x = torch.empty(3,3,device=device)
# #创建一个随机初始化的 3x3 张量
# x = torch.rand(3,3,device=device)

# #从另外一个张量创建张量
# #除非显式指定，否则新张量与原张量将保留参数张量的属性，形状与数据类型等

# x_ones = torch.ones_like(x,dtype=torch.float,device=device) #<—— 创建一个与 x 具有相同形状和类型的新张量，并用 1 填充它。
# print(f"Ones Tensor: \n {x_ones} \n")

# x_rand = torch.rand_like(x,dtype=torch.float,device=device)  #<—— 创建一个与 x 具有相同形状和类型的新张量，并用随机数填充它。
# print(f"Random Tensor: \n {x_rand} \n")
# #这里发现torch.float与float在torch中是不同的


# #使用随机或常量
# shape = (2,3,4,)
# rand_tensor = torch.rand(shape,device=device)
# ones_tensor = torch.ones(shape,device=device)
# zeros_tensor = torch.zeros(shape,device=device)
# print(f"Random Tensor: \n {rand_tensor} \n")
# print(f"Ones Tensor: \n {ones_tensor} \n")
# print(f"Zeros Tensor: \n {zeros_tensor} \n")


# # 2. 张量的属性
# #张量有四个属性：形状（shape）、数据类型（dtype）、设备（device）和 requires_grad（是否需要计算梯度）。
# tensor = torch.rand(3,3,dtype=torch.float,device=device)

# print(f"Shape of Tensor: {tensor.shape} \n")
# print(f"Data type of Tensor: {tensor.dtype} \n")
# print(f"Device of Tensor: {tensor.device} \n")


# # 3. 张量的操作
# #张量支持许多操作，包括数学运算、逻辑运算、比较运算等。这些操作可以在 CPU 或 GPU 上执行，具体取决于张量所在的设备。

# #默认情况下，PyTorch 会将张量在 CPU 上创建。如果要在 GPU 上创建张量，可以使用 .to(device) 方法。
# #需要注意的是，跨设备复制大型张量可能会付出巨大的内存和时间成本。因此，最好在创建张量时就指定其设备，以避免不必要的复制操作。
# #将tensor从cpu移动到gpu if availibe
# if torch.cuda.is_available():
#     tensor = tensor.to("cuda")

# #标准的类似于numpy 的索引与切片操作
# tensor = torch.rand((5,5,3),dtype=torch.float,device=device)
# print(f"First row 第2排: {tensor[1]}")
# print(f"First column 第3列: {tensor[:,:,2]}")
# print(f"Last row 最后一排: {tensor[-1]}")
# tensor[:,1] = 0  #<—— 将第二列的所有元素设置为 0。
# print(f"Modified Tensor 改变后的张量: \n {tensor} \n")

# #链接张量 您可以使用 torch.cat 函数将多个张量沿指定维度连接起来。

# tensor = torch.rand(2,3,dtype=torch.float,device=device)
# print(tensor)
# t1 = torch.cat([tensor,tensor,tensor],dim=1)
# print (t1)

# # 4.算术运算
# tensor = torch.rand(2,3,dtype=torch.float,device=device)

# y1 = tensor @ tensor.T #矩阵乘法
# y2 = tensor.matmul(tensor.T) #矩阵乘法

# y3 = torch.rand_like(y1)
# torch.matmul(tensor,tensor.T,out=y3) #out指定输出张量
# print(y1,"\n",y2,"\n",y3)

# z1 = tensor * tensor
# z2 = tensor.mul(tensor) #mul是element-wise multiplication

# z3 = torch.rand_like(tensor)
# torch.mul(tensor,tensor,out=z3) #out指定输出张量
# print(z1,"\n",z2,"\n",z3)

# tensor = torch.rand(3,3,dtype=torch.float,device=device)
# agg = tensor.sum()
# agg_item = agg.item()
# print(agg_item, type(agg_item))

# # 就地操作
# tensor = torch.ones(3,3,dtype=torch.float,device=device)
# print(f"{tensor}\n")
# tensor.add_(5)  # add_ 是一个就地操作，它将 5 添加到 tensor 的每个元素上，并返回 tensor 本身。
# print(f"tensor after add_: \n {tensor}")
# 与add_不同，add_ 不会创建新的张量，而是直接修改原始张量。因此，add_ 操作可以节省内存，但需要注意，它可能会覆盖原始张量的值。
# tensor = tensor.add(5)  # add 是一个非就地操作，它将 5 添加到 tensor 的每个元素上，并返回一个新的张量。
# print(tensor)


# 张量到numpy的转换
t = torch.ones(5)
# print(f"t: {t}")
n = t.numpy()
# print(f"n: numpy({n})")

#张量的变化会反映在numpy数组中。
t.add_(1)
# print(f"t: {t}")
# print(f"n: {n}") 

# numpy数组到张量的转换

n = np.ones(5)
t = torch.from_numpy(n)

print(n,"\n",t)

np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")

